/**
 * @description 使用 Pinia 实现的全局事件发布/订阅的机制
 */

/*** 目前需要将所有可能被触发的事件及其参数结构都预先定义在下面，便于管理和提供类型提示 ***/

/**
 * 事件类型
 */
export enum Event {
  /**
   * 显示顶部进度条
   */
  showTopLoader,
  /**
   * 隐藏顶部进度条
   */
  hideTopLoader,
}
/**
 * 事件的参数结构
 */
interface EventArgs {
  [Event.showTopLoader]: void;
  [Event.hideTopLoader]: void;
}

/**
 * 需要参数的事件
 */
type ArgsEvent = {
  [K in keyof EventArgs]: EventArgs[K] extends void | undefined ? never : K;
}[keyof EventArgs];

/**
 * 无需参数的事件
 */
type NoArgsEvent = {
  [K in keyof EventArgs]: EventArgs[K] extends void | undefined ? K : never;
}[keyof EventArgs];

/**
 * 事件监听器类型
 */
type EventCallback<E extends Event> = (args: EventArgs[E]) => void;

/**
 * 存储事件/监听器的 Pinia Store
 *
 * 不直接导出这个 Store，通过下面的组合式函数使用
 */
const useEventStore = defineStore('event', () => {
  const events = ref<{ [E in Event]?: EventCallback<any>[] }>({}); // 用于存储事件及其监听器

  // 监听事件
  function on<E extends Event>(event: E, callback: EventCallback<E>) {
    if (!events.value[event]) {
      events.value[event] = [];
    }
    events.value[event].push(callback);
  }

  // 触发事件
  function emit<E extends Event>(event: E, args?: EventArgs[E]) {
    if (events.value[event]) {
      events.value[event].forEach((callback) => {
        callback(args ?? undefined);
      });
    }
  }

  // 移除事件监听器
  function off<E extends Event>(event: E, callback: EventCallback<E>) {
    if (events.value[event]) {
      events.value[event] = events.value[event].filter((cb) => cb !== callback);
    }
  }

  return { on, emit, off };
});

/**
 * 监听一个全局事件，当事件被 `publishEvent` 触发时执行回调; 组件挂载时开始监听，组件卸载时自动移除监听
 * @param event 事件
 * @param callback 回调函数
 */
export function useEvent<E extends Event>(event: E, callback: EventCallback<E>) {
  onMounted(() => {
    useEventStore().on(event, callback);
  });
  onBeforeUnmount(() => {
    useEventStore().off(event, callback);
  });
}

/**
 * 触发一个全局事件，所有监听器都会收到通知
 * @param event 事件
 * @param args 事件参数
 */
export function publishEvent<E extends NoArgsEvent>(event: E): void;
export function publishEvent<E extends ArgsEvent>(event: E, args: EventArgs[E]): void;
export function publishEvent<E extends Event>(event: E, args?: EventArgs[E]): void {
  useEventStore().emit(event, args);
}
